import java.util.*;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Synthesizer;

public abstract class Scale
{
	protected List<Pitch> scale;
	
	public Pitch getRoot()
	{
		return scale.get(0);
	}
	
	public List<Pitch> getScale()
	{
		return scale;
	}
	
	/**
	 * @return the number of distinct notes in this scale
	 */
	public int getLength()
	{
		return scale.size() - 1;
	}
	
	public abstract Scale octaveUp();
	
	public List<Pitch> arpeggio()
	{
		List<Pitch> arpeggio = new ArrayList<Pitch>();
		
		arpeggio.add(scale.get(0));
		arpeggio.add(scale.get(2));
		arpeggio.add(scale.get(4));
		arpeggio.add(scale.get(7));
		
		return arpeggio;
	}
	
	public void play(int ms) throws MidiUnavailableException
	{
		Synthesizer synth = MidiSystem.getSynthesizer();
		synth.open();
		
		MidiChannel channel = synth.getChannels()[0];
		
		// probably acoustic piano on most systems
		channel.programChange(0);
		
		// main volume (cc=7) = max (127)
		channel.controlChange(7,127);
		
		for (Pitch e : scale)
		{
			int note = e.freq.ordinal()+Pitch.ORDINAL_MIDI_OFFSET;
			
			channel.noteOn(note,90);
			try {Thread.sleep(ms);}
			catch (InterruptedException ie) {ie.printStackTrace();}
			channel.noteOff(note);
		}
		
		// sustain pause
		try {Thread.sleep(1000);}
		catch (InterruptedException ie) {ie.printStackTrace();}
		
		channel.allNotesOff();
		synth.close();
	}
	
	public void arpeggiate(int ms) throws MidiUnavailableException
	{
		Synthesizer synth = MidiSystem.getSynthesizer();
		synth.open();
		
		MidiChannel channel = synth.getChannels()[0];
		
		// probably acoustic piano on most systems
		channel.programChange(0);
		
		// main volume (cc=7) = max (127)
		channel.controlChange(7,127);
		
		for (Pitch e : arpeggio())
		{
			int note = e.freq.ordinal()+Pitch.ORDINAL_MIDI_OFFSET;
			
			channel.noteOn(note,90);
			try {Thread.sleep(ms);}
			catch (InterruptedException ie) {ie.printStackTrace();}
			channel.noteOff(note);
		}
		
		// sustain pause
		try {Thread.sleep(1000);}
		catch (InterruptedException ie) {ie.printStackTrace();}
		
		channel.allNotesOff();
		synth.close();
	}
}
